﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace pHM.DVBLib.Common
{
    /// <summary>
    /// Misclanious utility and conversion functions
    /// </summary>
    public static class Misc
    {
        /// <summary>
        /// Gets a DataTime object from an NDS timestamp (as used in ECMs etc.)
        /// </summary>
        /// <param name="timeStamp">An NDS timestamp</param>
        /// <returns>DateTime object</returns>
        public static DateTime GetDateFromNdsTimestamp(byte[] timeStamp)
        {
            int year = (timeStamp[0] / 12) + 1997;
            int month = (timeStamp[0] % 12) + 1;
            int day = timeStamp[1];
            int hour = timeStamp[2] / 8;
            int mm = (0x100 * (timeStamp[2] - hour * 8) + timeStamp[3]) / 32;
            int ss = ((0x100 * (timeStamp[2] - hour * 8) + timeStamp[3]) - mm * 32) * 2;

            return new DateTime(year, month, day, hour, mm, ss, DateTimeKind.Utc);
        }

        /// <summary>
        /// Format a filesize into a string
        /// </summary>
        /// <param name="size"></param>
        /// <returns></returns>
        public static string FormatFileSize(ulong size)
        {
            if (size < 1024)
            {
                return string.Format("{0:0.##} Bytes", size);
            }
            else if (size < 1024 * 1024)
            {
                return string.Format("{0:0.##} KB", (double)size / 1024);
            }
            else if (size < 1024 * 1024 * 1024)
            {
                return string.Format("{0:0.##} MB", (double)size / 1024 / 1024);
            }
            else
            {
                return string.Format("{0:0.##} GB", (double)size / 1024 / 1024 / 1024);
            }
        }

        /// <summary>
        /// Bitwise XOR of two byte[]
        /// </summary>
        /// <param name="a">First byte[]</param>
        /// <param name="b">Second byte[]</param>
        /// <returns>a ^ b</returns>
        public static byte[] XOR(byte[] a, byte[] b)
        {
            byte[] output = new byte[a.Length];

            for (int i = 0; i <= a.GetUpperBound(0); i++)
            {
                output[i] = (byte)((int)a[i] ^ (int)b[i]);
            }
            return output;
        }

        /// <summary>
        /// Finds out if an int is even
        /// </summary>
        /// <param name="num"></param>
        /// <returns></returns>
        public static bool isEven(int num)
        {
            if ((num & 1) == 1) return false;
            else return true;
        }

        /// <summary>
        /// Gets a range of items from a byte aray
        /// </summary>
        /// <param name="x">The byte array</param>
        /// <param name="lower">The index of the first byte to return</param>
        /// <param name="upper">The index of the last byte to return</param>
        /// <returns>Byte array containing the range of bytes specified</returns>
        public static byte[] bArrayGetRange(byte[] x, int lower, int upper)
        {
            byte[] output = new byte[upper - lower +1];
            int j = 0;
            for (int i = lower; i <= upper; i++)
            {
                output[j] = x[i];
                j++;
            }
            return output;
        }

        /// <summary>
        /// Faster version of isByteArrayEqual using unsafe code
        /// </summary>
        /// <param name="a1"></param>
        /// <param name="a2"></param>
        /// <returns></returns>
        public static unsafe bool UnsafeIsByteArrayEqual(byte[] a1, byte[] a2)
        {
            if (a1 == null || a2 == null || a1.Length != a2.Length)
                return false;
            fixed (byte* p1 = a1, p2 = a2)
            {
                byte* x1 = p1, x2 = p2;
                int l = a1.Length;
                for (int i = 0; i < l / 8; i++, x1 += 8, x2 += 8)
                    if (*((long*)x1) != *((long*)x2)) return false;
                if ((l & 4) != 0) { if (*((int*)x1) != *((int*)x2)) return false; x1 += 4; x2 += 4; }
                if ((l & 2) != 0) { if (*((short*)x1) != *((short*)x2)) return false; x1 += 2; x2 += 2; }
                if ((l & 1) != 0) if (*((byte*)x1) != *((byte*)x2)) return false;
                return true;
            }
        }

        /// <summary>
        /// Tests two byte[] and finds out if they are equal
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static bool isByteArrayEqual(byte[] a, byte[] b)
        {
            if (a == null || a.Length != b.Length) return false;

            for (int i = 0; i < a.Length; i++)
            {
                if (a[i] != b[i]) return false;
            }

            return true;
        }

        /// <summary>
        /// Gets the specified bits from a short
        /// </summary>
        /// <param name="value"></param>
        /// <param name="startBit"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public static int GetBits(UInt16 value, int startBit, int count)
        {
            return (value >> startBit) & ((1 << count) - 1);
        }

        /// <summary>
        /// Converts a DOS date time to a DateTime
        /// </summary>
        /// <param name="date"></param>
        /// <param name="time"></param>
        /// <returns></returns>
        public static DateTime DosToDateTime(UInt16 date, UInt16 time)
        {
            int day = GetBits(date, 0, 5);
            int month = GetBits(date, 5, 4);
            int year = GetBits(date, 9, 7) + 1980;

            int seconds = GetBits(time, 0, 5) / 2;
            int minutes = GetBits(time, 5, 6);
            int hours = GetBits(time, 11, 5);

            if (day == 0) day = 1;
            if (month == 0) month = 1;

            DateTime Temp;

            if (DateTime.TryParse(string.Format("{0}-{1}-{2} {3}:{4}:{5}", year, month, day, hours, seconds, minutes), out Temp) == true)
                return Temp;
            else
                return new DateTime(1980, 1, 1);
        }



        public static ushort[] DateTimeToDos(DateTime dateTime)
        {
            ushort dosTime = (ushort)((((ushort)dateTime.Hour & 0x1F) << 11) | (((ushort)dateTime.Minute & 0x3F) << 5)
                            | ((ushort)(dateTime.Second / 2) & 0x1F));

            ushort dosDate = (ushort)((((ushort)(dateTime.Year - 1980) & 0x7F) << 9) | (((ushort)dateTime.Month & 0xF) << 5)
                            | ((ushort)(dateTime.Day) & 0x1F));

            return new ushort[] { dosTime, dosDate };
        }

        public static void CopyStream(Stream inStream, Stream outStream, long start, long length, int blockSize = 1024)
        {
            if (length == 0) length = inStream.Length - start;

            inStream.Seek(start, SeekOrigin.Begin);

            byte[] buffer = new byte[blockSize];
            long bytesCopied = 0;

            while ((length - bytesCopied) > blockSize)
            {
                bytesCopied += inStream.Read(buffer, 0, blockSize);
                outStream.Write(buffer, 0, blockSize);
            }

            inStream.Read(buffer, 0, (int)(length - bytesCopied));
            outStream.Write(buffer, 0, (int)(length - bytesCopied));
        }
    }
}
